﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Collections;
using System.Net.Sockets;
using System.IO;
using System.Web;
using System.Xml;

namespace Jy123.Common
{
    public class MSN
    {
        public class MSNConnection
        {
            // These are the numbers where the addresses wil appear in the arraylist
            // it is not the niced solution, but it works a lot faster
            // you can use some kind of IDictionary object to put the value behind a key name
            //
            // PASPORTURLS
            //private int DARREALM = 0;
            private int DALOGIN = 1;
            //private int DAREG = 2;
            //private int PROPERTIES = 3;
            //private int GENERALDIR = 4;
            //private int HELP = 5;
            //private int CONFIGVERSION = 6;

            private ArrayList PassportUrls;

            /// <summary>
            /// Every message from a client has a unique transactionID
            /// With every message this number will be increased
            /// </summary>
            private long _transactionID = 0;

            private TcpClient _socket;
            private NetworkStream _stream;
            //private StreamReader _reader;
            //private StreamWriter _writer;
            private Dictionary<string, string> _linkmans;
            protected string _UserName;
            protected string _ScreenName;

            protected bool DataAvailable
            {
                get { return _stream.DataAvailable; }
            }

            protected bool Connected
            {
                get { return _socket != null; }
            }

            public Dictionary<string, string> Linkmans
            {
                get { return _linkmans; }
            }

            public MSNConnection()
            {
                _linkmans = new Dictionary<string, string>();
            }
            /// <summary>
            /// Make a socket connection and streamers
            /// </summary>
            /// <param name="host">IP host to connect to</param>
            /// <param name="port">IP port to connect to</param>
            protected void ConnectSocket(string host, int port)
            {
                //Console.WriteLine("Connecting to " + host + ":" + port);
                //_transactionID = 0;
                _socket = new TcpClient(host, port);
                _stream = _socket.GetStream();
                //_reader = new StreamReader(_stream, Encoding.ASCII);
                //_writer = new StreamWriter(_stream, Encoding.ASCII);
                //_writer.AutoFlush = true;
            }

            /// <summary>
            /// Write a message to the current socket, this functions raises the transactionID
            /// </summary>
            /// <param name="line">The Message</param>
            /// <param name="writeNewLine">Determine if you write a line, or only a message (with or withhout ending character</param>
            private void WriteLine(string line, bool writeNewLine)
            {
                //Console.WriteLine("Writing: " + line);
                if (_stream.CanWrite)
                {
                    if (writeNewLine)
                        line += "\r\n";
                    byte[] myWriteBuffer = Encoding.ASCII.GetBytes(line);
                    _stream.Write(myWriteBuffer, 0, myWriteBuffer.Length);
                }

                /*         
                         if (writeNewLine)
                             _stream.Write(
                             _writer.WriteLine(line);
                         else
                             _writer.Write(line);
                 */
                // raise the transactionId
                _transactionID++;
            }

            /// <summary>
            /// Write a command to the server
            /// </summary>
            /// <param name="command">The first command</param>
            /// <param name="parameters">The parameters that have to be send to the server</param>
            /// <param name="bSendId">Sometimes you don't have to send an transactionID</param>
            protected void WriteCommand(string command, string parameters, bool bSendId)
            {
                string line;
                // check what type of format it should be
                if (bSendId)
                    line = string.Format("{0} {1} {2}", command, _transactionID, parameters);
                else
                    line = string.Format("{0} {1}", command, parameters);
                // Write the line
                WriteLine(line, true);
            }

            /// <summary>
            /// This function read the information from the streamreader
            /// and if it read something it returns a new ServerCommand object
            /// </summary>
            /// <returns>if there is something return new ServerCommand object</returns>
            protected ServerCommand ReadCommand()
            {
                string line = string.Empty;
                if (_stream.CanRead)
                {
                    byte[] myReadBuffer = new byte[1024];
                    StringBuilder myCompleteMessage = new StringBuilder();
                    int numberOfBytesRead = 0;

                    // Incoming message may be larger than the buffer size.
                    do
                    {
                        numberOfBytesRead = _stream.Read(myReadBuffer, 0, myReadBuffer.Length);

                        myCompleteMessage.AppendFormat("{0}", Encoding.UTF8.GetString(myReadBuffer, 0, numberOfBytesRead));

                    }
                    while (_stream.DataAvailable);
                    line = myCompleteMessage.ToString();
                }



                //string line = _reader.ReadLine();
                //Console.WriteLine("Reading: " + line);
                if (line == null)
                {
                    //Console.WriteLine("Nothing received");
                    return new ServerCommand();
                }
                else
                {
                    return new ServerCommand(line);
                }
            }

            /// <summary>
            /// This function closes the connection, if its open
            /// </summary>
            protected void Dispose()
            {
                if (_socket != null)
                {
                    //_reader.Close();
                    //_writer.Close();
                    _stream.Close();
                    _socket.Close();
                    _socket = null;
                }
            }
            public int Connect(string UserName, string UserPassword)
            {
                string host = "messenger.hotmail.com";
                int port = 1863;
                string ChallengeString;
                ServerCommand ServCom;

                try
                {
                    while (true)
                    {
                        ConnectSocket(host, port);

                        // Login and provide the current version you want to use
                        WriteCommand("VER", "MSNP9 CVRO", true);
                        ServCom = ReadCommand();

                        // check return value
                        if (ServCom.CommandName != "VER")
                        {
                            return 1;
                        }

                        // act like a real msn client and send some information
                        WriteCommand("CVR", "0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS " + UserName, true);
                        ServCom = ReadCommand();

                        // check return value
                        if (ServCom.CommandName != "CVR")
                        {
                            return 1;
                        }

                        // This indicates that you want to connect this user
                        WriteCommand("USR", "TWN I " + UserName, true);
                        ServCom = ReadCommand();

                        // If the server provided the USR answer, than you have got a challenges
                        // You need this challenge to get a valid clienticket
                        if (ServCom.CommandName == "USR")
                        {
                            ChallengeString = ServCom.Param(3);
                            break;
                        }

                        // if the command is nog XFR, then whe don't know what to do anymore
                        if (ServCom.CommandName != "XFR")
                        {
                            return 1;
                        }
                        else
                        {
                            //Console.WriteLine("Redirected to other server");
                        }

                        // If we get here, then we have to change servers, get the IP number
                        string[] arIP = ServCom.Param(2).Split(':');
                        host = arIP[0];
                        // Also get the portnumber 
                        port = int.Parse(arIP[1]);

                        // close the current connection
                        Dispose();
                    }

                    // Get a valid ticket 
                    string clientticket = GetClientTicket(UserPassword, UserName, ChallengeString);

                    if (clientticket == "401")
                    {
                        // if the code is 401, then most likely there was a error in 
                        // your username password combination
                        return 401;
                    }
                    else if (clientticket == "0")
                    {
                        // unknown error occured
                        return 1;
                    }
                    else
                    {
                        // finnaly send a valid ticket ID back to the server
                        WriteCommand("USR", "TWN S " + clientticket, true);
                        ServCom = ReadCommand();

                        // if the response is USR <trans> OK then we are succesfully connected
                        if (ServCom.CommandName != "USR" && ServCom.Param(1) != "OK")
                        {
                            return 1;
                        }
                        //_transactionID = 8;

                        // retrieve the correct username (is always the same, but who cares)
                        // and retrieve you screenname that you have provided last time
                        //_UserName = ServCom.Param(2);
                        //_ScreenName = ServCom.Param(3);

                        //WriteCommand("LST", "FL", true);
                        //ServCom = ReadCommand();

                        WriteCommand("SYN", "0", true);
                        System.Threading.Thread.Sleep(2000);
                        ServCom = ReadCommand();
                        GetLinkmans(ServCom);
                        // Change the status in online, so can other see you come online
                        //WriteCommand("CHG", "NLN", true);
                        //ServCom = ReadCommand();
                        Dispose();
                        return 0;
                    }
                }
                catch //(Exception ex)
                {
                    //Console.WriteLine("Unknown error occured errocode: " + ex.Message);
                    return 1;
                }
            }

            /// <summary>
            /// This function asks a valid login adres, to connect to
            /// </summary>
            /// <returns>true if succeed</returns>
            protected bool GetLoginServerAddres()
            {
                // Make a request to the server, this adresses are being used in the MSN messenger
                HttpWebRequest ServerRequest = (HttpWebRequest)WebRequest.Create("https://nexus.passport.com/rdr/pprdr.asp");
                // Get the result
                HttpWebResponse ServerResponse = (HttpWebResponse)ServerRequest.GetResponse();
                if (ServerResponse.StatusCode == HttpStatusCode.OK)
                {
                    string retrieveddata = ServerResponse.Headers.ToString();
                    PassportUrls = new ArrayList();
                    // Pick up the header en split
                    string[] result = ServerResponse.Headers.Get("PassportURLs").Split(',');
                    foreach (string s in result)
                    {
                        // The actual adres is provided behind the '=' sign
                        PassportUrls.Add(s.Substring(s.IndexOf('=') + 1));
                    }
                    ServerResponse.Close();
                    return true;
                }
                else
                {
                    ServerResponse.Close();
                    return false;
                }
            }


            /// <summary>
            /// This function connects to a login server to request a valid ticket,
            /// that will be used to login on the MSN servers
            /// </summary>
            /// <param name="Password">The password of the user, this is just plain text. The connection is HTTPS
            /// <param name="Username">The complete username</param>
            /// <param name="ChallengeString">A challenge string that you have got, wile connecting to a msn server
            /// <returns>a valid ticket, that you send back to the server to get connected</returns>
            protected string GetClientTicket(string Password, string Username, string ChallengeString)
            {
                // First get a valid login adres for the initial server
                if (GetLoginServerAddres())
                {
                    // On the position of DALOGIN is a valid URL, for login
                    string uri = "https://" + PassportUrls[DALOGIN];

                    HttpWebRequest ServerRequest;
                    HttpWebResponse ServerResponse;
                    try
                    {
                        while (true)
                        {

                            //Console.WriteLine("Connecting to:  " + uri);
                            // Make a new request
                            ServerRequest = (HttpWebRequest)HttpWebRequest.Create(uri);
                            ServerRequest.AllowAutoRedirect = false;
                            ServerRequest.Pipelined = false;
                            ServerRequest.KeepAlive = false;
                            ServerRequest.ProtocolVersion = new Version(1, 0);
                            // Send the authentication header
                            ServerRequest.Headers.Add("Authorization", "Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in=" + Username.Replace("@", "%40") + ",pwd=" + Password + "," + ChallengeString + "\n");
                            // Pick up the result
                            ServerResponse = (HttpWebResponse)ServerRequest.GetResponse();

                            // If the statuscode is OK, then there is a valid return
                            if (ServerResponse.StatusCode == HttpStatusCode.OK)
                            {
                                // Pick up the information of the authentications
                                string AuthenticationInfo = ServerResponse.Headers.Get("Authentication-Info");
                                // Get the startposition of the ticket id (note it is between two quotes)
                                int startposition = AuthenticationInfo.IndexOf('\'');
                                // Get the endposition 
                                int endposition = AuthenticationInfo.LastIndexOf('\'');
                                // Substract the startposition of the endposition
                                endposition = endposition - startposition;

                                // Close connection
                                ServerResponse.Close();

                                // Generate a new substring and return it
                                return AuthenticationInfo.Substring(startposition + 1, endposition - 1);

                            }
                            // If the statuscode is 302, then there is a redirect, read the new adres en connect again
                            else if (ServerResponse.StatusCode == HttpStatusCode.Found)
                            {
                                uri = ServerResponse.Headers.Get("Location");
                            }
                        }
                    }
                    catch (WebException e)
                    {
                        // If the statuscode is 401, then an exeption occurs
                        // Think that your password + username combination is not correct
                        // return number so that calling functions knows what to do
                        if (e.Status == WebExceptionStatus.ProtocolError)
                        {
                            return "401";
                        }
                        else
                        {
                            return "0";
                        }
                    }
                }
                return "0";
            }
            /// <summary>
            /// parse linkman
            /// </summary>
            /// <param name="line"></param>
            private void GetLinkmans(ServerCommand servCommand)
            {
                _linkmans.Clear();
                string _line = servCommand.Line;
                string[] a = _line.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (string s in a)
                {
                    string[] l = s.Split(' ');
                    if (l.Length == 5 && l[0] == "LST")
                    {
                        _linkmans.Add(l[1], l[2]);
                    }
                }
            }
        }

        /// <summary>
        /// This class is uses, to use the result of the server in a easy way
        /// </summary>
        public class ServerCommand
        {
            // The command name of the response
            private string _cmdID;
            // The entire response
            private string _line;
            // The parameters of the response (without transactionID, and divided with a space)
            private string[] _params;

            /// <summary>
            /// Constructor for parsing the result line from the streamreader
            /// </summary>
            /// <param name="line"></param>
            public ServerCommand(string line)
            {
                _line = line;
                // always 3 characters command
                _cmdID = line.Substring(0, 3);
                if (!(_cmdID == "QNG"))
                {
                    _params = line.Substring(4).Split(' ');
                }
            }

            /// <summary>
            /// This constructor makes a valid object
            /// but the command name idicates that there was something wrong
            /// </summary>
            public ServerCommand()
            {
                _line = "";
                _cmdID = "ERROR";
            }

            public string CommandName
            {
                get { return _cmdID; }
            }

            public string Param(int index)
            {
                return _params[index];
            }

            public int ParamCount
            {
                get { return _params.Length; }
            }

            public string Line
            {
                get { return _line; }
            }
        }
    }
}
